/* Authors: Cheng Xie and Kuangda Li
 * Programming Assignment 2, CSSE 351
 */

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Window.hpp>
#include <math.h>
#include <vector>
#include <iostream>
#include <string>
using namespace std;

#define RESOLUTION 512
#define M_PI 3.141592653

enum 
{
	EObjectID_Head,
	EObjectID_Eyes,
	EObjectID_Neck,
	EObjectID_Arms,
	EObjectID_Torso,
	EObjectID_legs,
	EObjectID_Ground,
	EObjectID_ball,
};

struct Point
{
	Point() : x(0), y(0) {}
	Point(float x, float y) : x(x), y(y) {}
	float x;
	float y;
};

Point operator+(const Point& lhs, const Point& rhs)
{
	return Point(lhs.x+rhs.x, lhs.y+rhs.y);
}

/* This helper function generates the vertices to build a circle
 * \param center: center of the circle
   \param radius: radius of the circle
   \param division: number of vertices to be generated
   \vertices: the output vertices generated
 */
void BuildCircleVertices(const Point& center, float radius, int division, Point* vertices)
{
	float unit = (float)(M_PI * 2 / division);
	for (int i = 0; i < division; i++)
	{
		vertices[i].x = center.x + cos(unit * i) * radius;
		vertices[i].y = center.y + sin(unit * i) * radius;
	}
}

class GLBox
{
public:
	GLBox() : mSelectedObject(-1)
	{
		// Create the main window
		App = new sf::Window(sf::VideoMode(RESOLUTION, RESOLUTION, 32), "Test Line", sf::Style::Close);

		this->gfxInit();
		mSelectedObject = -1;

		while (App->IsOpened())
		{			
			sf::Event event;
			while (App->GetEvent(event))
			{
				if (event.Type == sf::Event::Closed)
				{
					App->Close();
				}
				
				if (App->GetInput().IsMouseButtonDown(sf::Mouse::Button::Left))
				{
					const sf::Input& Input = App->GetInput();

					int x = Input.GetMouseX();
					int y = Input.GetMouseY();

					y = RESOLUTION - y - 1;

					mMousePosition.x = x;
					mMousePosition.y = y;

					GLuint buffer[1024] = {0};
					glSelectBuffer(1024, buffer);

					glRenderMode(GL_SELECT);

					glMatrixMode(GL_PROJECTION);
					glPushMatrix();
					glLoadIdentity();

					GLint vp[4];
					glGetIntegerv(GL_VIEWPORT, vp);

					gluPickMatrix(x, y, 10, 10, vp);
					gluOrtho2D(0, RESOLUTION-1, 0, RESOLUTION-1);
					
					drawGeometry(true);

					glMatrixMode(GL_PROJECTION);
					glPopMatrix();
					glFlush();

					int hits = glRenderMode(GL_RENDER);

					if (hits)
					{
						mSelectedObject = buffer[3];
					}
					else 
					{
						mSelectedObject = -1;
					}
				}
				else 
				{
					mSelectedObject = -1;
				}
			}

			// Set the active window before using OpenGL commands
			// It's not needed here because active window is always the same,
			// but don't forget it if you use multiple windows or controls
			App->SetActive();
			
			// Render the scene
			this->display();
						
			// Finally, display rendered frame on screen
			App->Display();
		}
	}
	
	~GLBox()
	{
	}

	void gfxInit()
	{
		// Load the identity matrix onto the top of the projection stack
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		// Multiply a viewport transformation that matches the window size
		// onto the top of the projection stack
		gluOrtho2D(0, RESOLUTION-1, 0, RESOLUTION-1);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	}

	void display()
	{
		glClear(GL_COLOR_BUFFER_BIT);

		drawGeometry(false);
		drawUI();
	}

private:
	sf::Window *App;
	int mSelectedObject;
	Point mMousePosition;

	void drawUI()
	{
		if (mSelectedObject == -1)
		{
			return;
		}

		string text = "YOU PICKED THE ";
		string item;
		switch (mSelectedObject)
		{
		case EObjectID_Head:
			item = "HEAD";
			break;
		case EObjectID_Eyes:
			item = "EYE";
			break;
		case EObjectID_Arms:
			item = "ARM";
			break;
		case EObjectID_ball:
			item = "BALL";
			break;
		case EObjectID_Ground:
			item = "GROUND";
			break;
		case EObjectID_legs:
			item = "LEG";
			break;
		case EObjectID_Neck:
			item = "NECK";
			break;
		case EObjectID_Torso:
			item = "TORSO";
			break;
		}

		text += item;

		const sf::Input& input = App->GetInput();

		drawText(mMousePosition.x - 80, mMousePosition.y - 40, text.c_str());
	}

	void drawText(int x, int y, const char* text)
	{
		// These character bitmap data is from Internet
		static GLubyte letters[][13] = {
			{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18}, 
			{0x00, 0x00, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe}, 
			{0x00, 0x00, 0x7e, 0xe7, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e}, 
			{0x00, 0x00, 0xfc, 0xce, 0xc7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xce, 0xfc}, 
			{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xff}, 
			{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xff}, 
			{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e}, 
			{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3}, 
			{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e}, 
			{0x00, 0x00, 0x7c, 0xee, 0xc6, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06}, 
			{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xe0, 0xf0, 0xd8, 0xcc, 0xc6, 0xc3}, 
			{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0}, 
			{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xdb, 0xff, 0xff, 0xe7, 0xc3}, 
			{0x00, 0x00, 0xc7, 0xc7, 0xcf, 0xcf, 0xdf, 0xdb, 0xfb, 0xf3, 0xf3, 0xe3, 0xe3}, 
			{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0x7e}, 
			{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe}, 
			{0x00, 0x00, 0x3f, 0x6e, 0xdf, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c}, 
			{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe}, 
			{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0xe0, 0xc0, 0xc0, 0xe7, 0x7e}, 
			{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff}, 
			{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3}, 
			{0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3}, 
			{0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3}, 
			{0x00, 0x00, 0xc3, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3}, 
			{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3}, 
			{0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x7e, 0x0c, 0x06, 0x03, 0x03, 0xff},
		};

		glRasterPos2i(x, y);

		char *p = (char *)text;
		while (*p)
		{
			if ('A' <= *p && *p <= 'Z')
			{
				GLubyte *data;
				int index = *p - 'A';
				data = letters[index];
				glBitmap(8, 13, 0, 2, 10, 0, data);
			}
			else 
			{
				glBitmap(0, 0, 0, 0, 10, 0, 0);
			}
			p++;
		}

		glFlush();
	}

	void drawGeometry(bool selectMode)
	{
		const int division = 32;
		Point vertices[division];
		float y = 340;
		float x = 256;
		Point center(x, y);
		float radius = 32;
		BuildCircleVertices(center, radius, division, vertices);

		// draw the head
		if (selectMode) glPushName(EObjectID_Head);
		glBegin(GL_LINE_LOOP);
		for (int i = 0; i < division; i++)
		{
			glColor3f(1.0f, 1.0f, 1.0f);
			glVertex2f(vertices[i].x, vertices[i].y);
		}
		glEnd();
		if (selectMode) glPopName();

		// draw the eyes
		if (selectMode) glPushName(EObjectID_Eyes);
		glPointSize(6);
		glBegin(GL_POINTS);
		glColor3f(1.0f, 1.0f, 0.0f);
		glVertex2f(x - 10, y + 10);
		glVertex2f(x + 10, y + 10);
		glColor3f(1.0f, 1.0f, 1.0f);
		glEnd();
		if (selectMode) glPopName();

		// draw the neck
		if (selectMode) glPushName(EObjectID_Neck);
		glBegin(GL_LINES);
		y -= 32;
		glVertex2f(x, y);
		y -= 24;
		glVertex2f(x, y);
		glVertex2f(0, 0); // redundant vertex for test
		glEnd();
		if (selectMode) glPopName();

		// draw the torso
		if (selectMode) glPushName(EObjectID_Torso);
		glBegin(GL_QUADS);
		x -= 16;
		glColor3f(1.0f, 0, 0);
		glVertex2f(x, y);
		x += 32;
		glColor3f(0, 1.0f, 0);
		glVertex2f(x, y);
		y -= 100;
		glColor3f(0, 0, 1.0f);
		glVertex2f(x, y);
		x -= 32;
		glColor3f(1.0f, 1.0f, 1.0f);
		glVertex2f(x, y);
		glEnd();
		if (selectMode) glPopName();

		// draw the arms
		if (selectMode) glPushName(EObjectID_Arms);
		x += 16;
		glBegin(GL_LINES);
		glVertex2f(x + 16, y + 85);
		glVertex2f(x + 84, y + 55);
		glVertex2f(x - 16, y + 85);
		glVertex2f(x - 84, y + 55);
		glEnd();
		if (selectMode) glPopName();

		// draw the legs
		if (selectMode) glPushName(EObjectID_legs);
		glBegin(GL_LINES);
		glVertex2f(x - 6, y);
		glVertex2f(x - 24, y - 80);
		glVertex2f(x + 6, y);
		glVertex2f(x + 24, y - 80);
		glEnd();
		if (selectMode) glPopName();

		// draw the ground
		if (selectMode) glPushName(EObjectID_Ground);
		y -= 80;
		glBegin(GL_POLYGON);
		glVertex2f(x - 200, y - 15);
		glVertex2f(x + 200, y - 15);
		glVertex2f(x + 250, y + 15);
		glVertex2f(x - 150, y + 15);
		glEnd();
		if (selectMode) glPopName();

		// draw the ball

		static Point ballCenter(x + 80, y + 110);
		static float speed = -40;

		if (ballCenter.y < 130) {
			speed = 40;
		}
		if (ballCenter.y > 215) {
			speed = -40;
		}
		
		float dt = App->GetFrameTime();
		ballCenter.y += dt * speed;

		float ballRadius = 20;
		const int ballDivision = 12;
		Point ballVertices[ballDivision];
		BuildCircleVertices(ballCenter, ballRadius, ballDivision, ballVertices);

		if (selectMode) glPushName(EObjectID_ball);
		glBegin(GL_TRIANGLE_FAN);
		glVertex2f(ballCenter.x, ballCenter.y);
		for (int i = 0; i <= ballDivision; i++)
		{
			glVertex2f(ballVertices[i % ballDivision].x, ballVertices[i % ballDivision].y);
		}
		glEnd();
		if (selectMode) glPopName();
	}
};

int main()
{
	GLBox prog;
	
    return EXIT_SUCCESS;
}
